package org.ghost.springboot.demo.util.http;

import com.alibaba.fastjson.util.IOUtils;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.*;
import org.apache.http.auth.AuthScope;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.TrustStrategy;
import org.apache.http.util.EntityUtils;
import org.ghost.springboot.demo.dto.Tuple2;
import org.ghost.springboot.demo.util.FileUtil;
import org.ghost.springboot.demo.util.JacksonUtil;
import org.ghost.springboot.demo.util.MimeTypeUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.util.StreamUtils;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.*;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class HttpClientHelper {
    private static final Logger logger = LoggerFactory.getLogger(HttpClientHelper.class);
    private static final String NEW_LINE = "\r\n";
    private static final String BOUNDARY_PREFIX = "--";
    private static TrustManager X509TrustManagerEx = new X509TrustManager() {

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType)
                throws CertificateException {
        }

        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType)
                throws CertificateException {
        }
    };

    /**
     * 数据分隔线
     *
     * @return
     */
    private static String getBoundary() {
        String boundary = "----WebKitFormBoundary8RPsnNWd31e38HLk" + System.currentTimeMillis();
        return boundary;
    }

    public static CloseableHttpClient createSSLClientDefault() {
        try {
            SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
                //信任所有
                @Override
                public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                    return true;
                }
            }).build();
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);
            return HttpClients.custom().setSSLSocketFactory(sslsf).build();
        } catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
            logger.error("*****createSSLClientDefault出现异常{}", e);
        }

        return HttpClients.createDefault();
    }

    public static byte[] downloadFile(String url) {
        int byteRead = 0;
        ByteArrayOutputStream fs = null;
        HttpURLConnection conn = createHttpURLConnection(url);
        try {
            InputStream inStream = conn.getInputStream();
            fs = new ByteArrayOutputStream(2048);

            byte[] buffer = new byte[1204];
            while ((byteRead = inStream.read(buffer)) != -1) {
                fs.write(buffer, 0, byteRead);
            }

            IOUtils.close(inStream);
            return fs.toByteArray();
        } catch (Exception e) {
            logger.error("*****HttpClientHelper.downloadFile下载文件出现异常", e);
        } finally {
            IOUtils.close(fs);
        }

        return null;
    }

    /**
     * 模拟表单文件上传
     *
     * @param url
     * @param params
     * @param file
     * @param t
     * @param <T>
     * @return
     */
    public static <T> T doPostFile(String url, Map<String, Object> params, File file, Class<T> t) throws IOException {
        HttpURLConnection httpConn = createHttpURLConnection(url);
        return doPostFile(httpConn, params, file, t);
    }

    /**
     * 模拟表单文件上传
     *
     * @param url
     * @param params
     * @param fileName
     * @param data
     * @param t
     * @param <T>
     * @return
     */
    public static <T> T doPostFile(String url, Map<String, Object> params, String fileName, byte[] data, Class<T> t) {
        HttpURLConnection httpConn = createHttpURLConnection(url);
        return doPostFile(httpConn, params, fileName, data, t);
    }

    private static HttpURLConnection createHttpURLConnection(String url) {
        HttpURLConnection httpConn = null;
        if (StringUtils.isBlank(url)) {
            return null;
        }

        if (url.toLowerCase().startsWith("https")) {
            try {
                //设置SSLContext
                SSLContext sslContext = SSLContext.getInstance("TLS");
                sslContext.init(null, new TrustManager[]{X509TrustManagerEx}, null);

                URL requestUrl = new URL(url);
                HttpsURLConnection conn = (HttpsURLConnection) requestUrl.openConnection();

                //设置套接工厂
                conn.setSSLSocketFactory(sslContext.getSocketFactory());

                httpConn = conn;
            } catch (Exception e) {
                logger.error("*****HttpClientHelper.doPostFile创建HttpsURLConnection出现异常", e);
            }
        } else {
            try {
                URL requestUrl = new URL(url);
                httpConn = (HttpURLConnection) requestUrl.openConnection();
            } catch (Exception e) {
                logger.error("*****HttpClientHelper.doPostFile创建HttpURLConnection出现异常", e);
            }
        }

        return httpConn;
    }

    private static <T> T doPostFile(HttpURLConnection conn, Map<String, Object> params, File file, Class<T> t) throws
            IOException {
        String fileName = file.getName();
        byte[] data = org.aspectj.util.FileUtil.readAsByteArray(file);
        return doPostFile(conn, params, fileName, data, t);
    }

    private static <T> T doPostFile(HttpURLConnection conn, Map<String, Object> params, String fileName, byte[] data,
                                    Class<T> t) {
        try {
            // 定义数据分隔线
            String boundary = getBoundary();
            StringBuffer stringBuffer = new StringBuffer("");
            conn.setRequestMethod("POST");
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setUseCaches(false);

            // 设置请求头参数
            conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.6)");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("Charsert", "UTF-8");
            conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);

            OutputStream out = new DataOutputStream(conn.getOutputStream());
            //其他参数
            if (params != null) {
                StringBuffer strBuf = new StringBuffer();
                for (Map.Entry<String, Object> item : params.entrySet()) {
                    String inputName = item.getKey();
                    String inputValue = item.getValue() == null ? "" : item.getValue().toString();
                    if (inputValue == null) {
                        continue;
                    }
                    strBuf.append(NEW_LINE).append("--").append(boundary).append(NEW_LINE);
                    strBuf.append("Content-Disposition: form-data; name=\"").append(inputName).append("\"").append(NEW_LINE).append(NEW_LINE);
                    strBuf.append(inputValue);
                }
                stringBuffer.append(strBuf.toString());
                out.write(strBuf.toString().getBytes());
            }
            // 文件参数
            if (data != null) {
                //没有传入文件类型，同时根据文件获取不到类型，默认采用application/octet-stream
                String suffixName = FileUtil.getSuffixName(fileName);
                String contentType = MimeTypeUtil.getMimeType(suffixName);
                if (StringUtils.isBlank(contentType)) {
                    contentType = "application/octet-stream";
                }
                StringBuffer strBuf = new StringBuffer();
                strBuf.append(NEW_LINE).append("--").append(boundary).append(NEW_LINE);
                strBuf.append("Content-Disposition: form-data; name=\"file\"; filename=\"" + fileName + "\"").append(NEW_LINE);
                strBuf.append("Content-Type:").append(contentType).append(NEW_LINE).append(NEW_LINE);
                stringBuffer.append(strBuf.toString());
                out.write(strBuf.toString().getBytes());
                stringBuffer.append(new String(data));
                out.write(data, 0, data.length);
            }

            // 定义最后数据分隔线，即--加上BOUNDARY再加上--。
            byte[] endData = (NEW_LINE + BOUNDARY_PREFIX + boundary + BOUNDARY_PREFIX + NEW_LINE).getBytes();
            // 写上结尾标识
            stringBuffer.append(NEW_LINE + BOUNDARY_PREFIX).append(boundary).append(BOUNDARY_PREFIX).append(NEW_LINE);
            out.write(endData);
            out.flush();
            out.close();

            //读取返回数据
            String res = org.apache.commons.io.IOUtils.toString(conn.getInputStream(), "UTF-8");
            org.apache.commons.io.IOUtils.close(conn);
            if (logger.isInfoEnabled()) {
                logger.info("*****HttpClientHelper.doPostFile请求url:{},结果:{}", conn.getURL().toString(), res);
            }
            return JacksonUtil.nonEmptyMapper().fromJson(res, t);
        } catch (Exception e) {
            logger.error("*****HttpClientHelper.doPostFile出现异常:{},{}", e.getMessage(), e);
        }

        return null;
    }

    public static Map doPost(String url, Map params) {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpPost httpPost = postForm(url, params);
        String res = invoke(httpClient, httpPost);
        return parseType(res, Map.class);
    }

    public static <T> T doPost(String url, Map params, Class<T> t) {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpPost httpPost = postForm(url, params);
        String res = invoke(httpClient, httpPost);
        return JacksonUtil.nonEmptyMapper().fromJson(res, t);
    }


    public static <T> T doPostWithJson(String url, Object params, Class<T> t) {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpPost httpPost = postJson(url, params);
        String res = invoke(httpClient, httpPost);
        return JacksonUtil.nonEmptyMapper().fromJson(res, t);
    }


    /**
     * doPostWithJson
     *
     * @param url
     * @param params
     * @param typeReference TypeReference<T> typeReference = new TypeReference<T>() {};
     * @param <T>
     * @return
     */
    public static <T> T doPostWithJson(String url, Object params, TypeReference<T> typeReference) {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpPost httpPost = postJson(url, params);
        String res = invoke(httpClient, httpPost);
        return JacksonUtil.nonEmptyMapper().fromJson(res, typeReference);
    }

    /**
     * doPostWithJson
     *
     * @param url
     * @param params
     * @param parameterizedType 例如:ParameterizedType parameterizedType = ParameterizedTypeImpl.make(HttpRspDTO.class, new Type[]{CustomerKeyInfoRspDTO.class}, null);
     * @param <T>
     * @return
     */
    public static <T> T doPostWithJson(String url, Object params, ParameterizedType parameterizedType) {
        TypeReference<T> typeReference = new TypeReference<T>() {
            @Override
            public Type getType() {
                return parameterizedType;
            }
        };
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpPost httpPost = postJson(url, params);
        String res = invoke(httpClient, httpPost);
        return JacksonUtil.nonEmptyMapper().fromJson(res, typeReference);
    }

    /**
     * doPostWithJson
     *
     * @param url
     * @param params
     * @param rawType   ParameterizedTypeImpl.make 第一个参数
     * @param ownerType ParameterizedTypeImpl.make 第三个参数
     * @param argType   ParameterizedTypeImpl.make 第二个参数
     * @param <T>
     * @return
     */
    public static <T> T doPostWithJson(String url, Object params, Class rawType, Type ownerType, Type... argType) {
        ParameterizedType parameterizedType = ParameterizedTypeImpl.make(rawType, argType, ownerType);
        TypeReference<T> typeReference = new TypeReference<T>() {
            @Override
            public Type getType() {
                return parameterizedType;
            }
        };

        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpPost httpPost = postJson(url, params);
        String res = invoke(httpClient, httpPost);
        return JacksonUtil.nonEmptyMapper().fromJson(res, typeReference);
    }

    public static <T> T doGet(String url, Map params, Class<T> t) {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpGet httpGet = getForm(url, params);
        String res = invoke(httpClient, httpGet);
        return JacksonUtil.nonEmptyMapper().fromJson(res, t);
    }

    public static Map doGet(String url, Map params) {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpGet httpGet = getForm(url, params);
        String res = invoke(httpClient, httpGet);
        return JacksonUtil.nonEmptyMapper().fromJson(res, Map.class);
    }

    /**
     * doGet
     *
     * @param url
     * @param params
     * @param typeReference TypeReference<T> typeReference = new TypeReference<T>() {};
     * @param <T>
     * @return
     */
    public static <T> T doGet(String url, Map params, TypeReference<T> typeReference) {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpGet httpGet = getForm(url, params);
        String res = invoke(httpClient, httpGet);
        return JacksonUtil.nonEmptyMapper().fromJson(res, typeReference);
    }

    /**
     * doGet
     *
     * @param url
     * @param params
     * @param parameterizedType 例如:ParameterizedType parameterizedType = ParameterizedTypeImpl.make(HttpRspDTO.class, new Type[]{CustomerKeyInfoRspDTO.class}, null);
     * @param <T>
     * @return
     */
    public static <T> T doGet(String url, Map params, ParameterizedType parameterizedType) {
        TypeReference<T> typeReference = new TypeReference<T>() {
            @Override
            public Type getType() {
                return parameterizedType;
            }
        };
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpGet httpGet = getForm(url, params);
        String res = invoke(httpClient, httpGet);
        return JacksonUtil.nonEmptyMapper().fromJson(res, typeReference);
    }

    /**
     * doGet
     *
     * @param url
     * @param params
     * @param rawType   ParameterizedTypeImpl.make 第一个参数
     * @param ownerType ParameterizedTypeImpl.make 第三个参数
     * @param argType   ParameterizedTypeImpl.make 第二个参数
     * @param <T>
     * @return
     */
    public static <T> T doGet(String url, Map params, Class rawType, Type ownerType, Type... argType) {
        ParameterizedType parameterizedType = ParameterizedTypeImpl.make(rawType, argType, ownerType);
        TypeReference<T> typeReference = new TypeReference<T>() {
            @Override
            public Type getType() {
                return parameterizedType;
            }
        };

        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpGet httpGet = getForm(url, params);
        String res = invoke(httpClient, httpGet);
        return JacksonUtil.nonEmptyMapper().fromJson(res, typeReference);
    }

//    public static <T> T doGetXml(String url, Map<String, String> params, Class<T> t) {
//        CloseableHttpClient httpClient = HttpClients.createDefault();
//        HttpGet httpGet = getForm(url, params);
//        String res = invoke(httpClient, httpGet);
//
//        //return XmlUtil.convertXmlStrToGenericObject(t, res);
//        return XmlDeserializerFactory.getInstance().getDeserializer(t).deserializer(res);
//    }

    public static HttpResponse execute(HttpUriRequest httpUriRequest) {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        return sendRequest(httpClient, httpUriRequest);
    }

    private static HttpPost postForm(String url, Map<String, Object> params) {
        HttpPost httpPost = new HttpPost(url);
        List<NameValuePair> nvps = new ArrayList<NameValuePair>();

        Set<String> keySet = params.keySet();
        for (String key : keySet) {
            Object obj = params.get(key);
            if (obj != null) {
                nvps.add(new BasicNameValuePair(key, obj.toString()));
            }
        }

        try {
            httpPost.setEntity(new UrlEncodedFormEntity(nvps, "UTF-8"));
        } catch (Exception e) {
            logger.error("*****HttpClientHelper.postForm出现错误:", e);
        }

        return httpPost;
    }

    private static HttpGet getForm(String url, Map<String, String> params) {
        StringBuilder sb = new StringBuilder();
        if (!params.isEmpty()) {
            try {
                for (Map.Entry<String, String> item : params.entrySet()) {
                    String key = URLEncoder.encode(item.getKey(), "utf-8");
                    String value = URLEncoder.encode(item.getValue(), "utf-8");
                    if (StringUtils.isNotBlank(key)) {
                        sb.append("&").append(key).append("=").append(value);
                    }
                }
            } catch (Exception ex) {
                logger.error("*****getForm:{}", ex);
            }
        }
        if (sb.length() > 0) {
            if (url.indexOf('?') > 0) {
                url += sb.toString();
            } else {
                url += "?" + sb.substring(1);
            }
        }
        return new HttpGet(url);
    }

    private static HttpPost postJson(String url, Object obj) {
        HttpPost httpPost = new HttpPost(url);
        try {
            httpPost.setHeader("Content-Type", "application/json");
            ObjectMapper objectMapper = new ObjectMapper();
            StringEntity se = new StringEntity(objectMapper.writeValueAsString(obj), Charset.forName("UTF-8"));
            httpPost.setEntity(se);
        } catch (Exception e) {
            logger.error("*****postJson执行IO异常,错误信息: {}, {}", e.getMessage(), e);
        }

        return httpPost;
    }

    private static HttpPut putJson(String url, Object obj) {
        HttpPut httpPut = new HttpPut(url);
        try {
            httpPut.setHeader("Content-Type", "application/json");
            ObjectMapper objectMapper = new ObjectMapper();
            StringEntity se = new StringEntity(objectMapper.writeValueAsString(obj));
            httpPut.setEntity(se);
        } catch (Exception e) {
            logger.error("*****putJson执行IO异常,错误信息: {}, {}", e.getMessage(), e);
        }

        return httpPut;
    }

    private static String invoke(CloseableHttpClient httpclient, HttpUriRequest httpPost) {
        long begin = System.currentTimeMillis();

        HttpResponse response = sendRequest(httpclient, httpPost);
        String body = parseResponse(response);

        long end = System.currentTimeMillis();

        if (httpPost instanceof HttpPost) {
            HttpPost temp = (HttpPost) httpPost;
            try {
                String requestBody = StreamUtils.copyToString(temp.getEntity().getContent(), Charset.forName("UTF-8"));
                logger.info("*****method:{},url:{},time:{},request:{},response:{}", httpPost.getMethod(), httpPost.getURI().toString(), (end - begin) / 1000.0, requestBody, body);
            } catch (IOException e) {
                logger.error("invoke出现错误,错误信息: {}, {}", e.getMessage(), e);
            }
        } else {
            logger.info("*****method:{},url:{},time:{},response:{}", httpPost.getMethod(), httpPost.getURI().toString(), (end - begin) / 1000.0, body);
        }

        return body;
    }

    private static HttpResponse sendRequest(CloseableHttpClient httpclient, HttpUriRequest httpost) {
        HttpResponse response = null;
        try {
            response = httpclient.execute(httpost);
        } catch (IOException e) {
            if (logger.isErrorEnabled()) {
                logger.error("*****HTTP 执行IO异常,错误信息: {}, {}", e.getMessage(), e);
            }
        }

        return response;
    }

    public static String parseResponse(HttpResponse response) {
        HttpEntity entity = response.getEntity();
        String body = null;
        try {
            body = EntityUtils.toString(entity);
        } catch (ParseException | IOException e) {
            logger.error("*****HttpClientHelper.parseResponse出现错误:", e);
        }

        return body;
    }

    public static <T> T parseType(String mess, Class<T> t) {
        return JacksonUtil.useDefaultMapper().fromJson(mess, t);
    }

    public static <T> List<T> parseType2List(String mess, Class<T> cls) {
        JavaType javaType = JacksonUtil.useDefaultMapper().contructCollectionType(ArrayList.class, cls);
        return JacksonUtil.useDefaultMapper().fromJson(mess, javaType);
    }

    private static CloseableHttpClient getHttpClient(HttpRequestBuilder builder) {
        if (builder.getCredentials() != null) {
            HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
            CredentialsProvider provider = new BasicCredentialsProvider();
            provider.setCredentials(AuthScope.ANY, builder.getCredentials());
            httpClientBuilder.setDefaultCredentialsProvider(provider);

            return httpClientBuilder.build();
        } else {
            return HttpClients.createDefault();
        }
    }


    /**
     * invoke
     *
     * @param builder
     */
    public static void invoke(HttpRequestBuilder builder) {
        long begin = System.currentTimeMillis();
        Tuple2<Integer, String> tupleRsp = new Tuple2<Integer, String>(HttpStatus.OK.value(), "");
        try (CloseableHttpClient httpClient = getHttpClient(builder)) {
            try (CloseableHttpResponse response = httpClient.execute(builder.getHttpRequestBase())) {
                tupleRsp = getResponse(builder, response);
            }
        } catch (IOException e) {
            logger.error("*****HttpClientHelper.invoke出现错误:", e);
        } finally {
            writeLog(builder.getHttpRequestBase(), begin, tupleRsp);
        }
    }

    /**
     * invoke
     *
     * @param builder
     * @param cls
     * @param <T>
     * @return
     */
    public static <T> T invoke(HttpRequestBuilder builder, Class<T> cls) {
        long begin = System.currentTimeMillis();
        Tuple2<Integer, String> tupleRsp = new Tuple2<Integer, String>(HttpStatus.OK.value(), "");
        try (CloseableHttpClient httpClient = getHttpClient(builder)) {
            try (CloseableHttpResponse response = httpClient.execute(builder.getHttpRequestBase())) {
                tupleRsp = getResponse(builder, response);
                if (StringUtils.isNotBlank(tupleRsp.getSecond())) {
                    return JacksonUtil.nonEmptyMapper().fromJson(tupleRsp.getSecond(), cls);
                }
            }
        } catch (IOException e) {
            logger.error("*****HttpClientHelper.invoke出现错误:", e);
        } finally {
            writeLog(builder.getHttpRequestBase(), begin, tupleRsp);
        }

        return null;
    }


    /**
     * invoke
     *
     * @param builder
     * @param typeReference TypeReference<T> typeReference = new TypeReference<T>() {};
     * @param <T>
     * @return
     */
    public static <T> T invoke(HttpRequestBuilder builder, TypeReference<T> typeReference) {
        long begin = System.currentTimeMillis();
        Tuple2<Integer, String> tupleRsp = new Tuple2<Integer, String>(HttpStatus.OK.value(), "");
        try (CloseableHttpClient httpClient = getHttpClient(builder)) {
            try (CloseableHttpResponse response = httpClient.execute(builder.getHttpRequestBase())) {
                tupleRsp = getResponse(builder, response);
                if (StringUtils.isNotBlank(tupleRsp.getSecond())) {
                    return JacksonUtil.nonEmptyMapper().fromJson(tupleRsp.getSecond(), typeReference);
                }
            }
        } catch (IOException e) {
            logger.error("*****HttpClientHelper.invoke出现错误:", e);
        } finally {
            writeLog(builder.getHttpRequestBase(), begin, tupleRsp);
        }

        return null;
    }

    /**
     * invoke
     *
     * @param builder
     * @param parameterizedType 例如:ParameterizedType parameterizedType = ParameterizedTypeImpl.make(HttpRspDTO.class, new Type[]{CustomerKeyInfoRspDTO.class}, null);
     * @param <T>
     * @return
     */
    public static <T> T invoke(HttpRequestBuilder builder, ParameterizedType parameterizedType) {
        TypeReference<T> typeReference = new TypeReference<T>() {
            @Override
            public Type getType() {
                return parameterizedType;
            }
        };

        long begin = System.currentTimeMillis();
        Tuple2<Integer, String> tupleRsp = new Tuple2<Integer, String>(HttpStatus.OK.value(), "");
        try (CloseableHttpClient httpClient = getHttpClient(builder)) {
            try (CloseableHttpResponse response = httpClient.execute(builder.getHttpRequestBase())) {
                tupleRsp = getResponse(builder, response);
                if (StringUtils.isNotBlank(tupleRsp.getSecond())) {
                    return JacksonUtil.nonEmptyMapper().fromJson(tupleRsp.getSecond(), typeReference);
                }
            }
        } catch (IOException e) {
            logger.error("*****HttpClientHelper.invoke出现错误:", e);
        } finally {
            writeLog(builder.getHttpRequestBase(), begin, tupleRsp);
        }

        return null;
    }

    /**
     * invoke
     *
     * @param builder
     * @param rawType   ParameterizedTypeImpl.make 第一个参数
     * @param ownerType ParameterizedTypeImpl.make 第三个参数
     * @param argType   ParameterizedTypeImpl.make 第二个参数
     * @param <T>
     * @return
     */
    public static <T> T invoke(HttpRequestBuilder builder, Class rawType, Type ownerType, Type... argType) {
        ParameterizedType parameterizedType = ParameterizedTypeImpl.make(rawType, argType, ownerType);
        TypeReference<T> typeReference = new TypeReference<T>() {
            @Override
            public Type getType() {
                return parameterizedType;
            }
        };

        long begin = System.currentTimeMillis();
        Tuple2<Integer, String> tupleRsp = new Tuple2<Integer, String>(HttpStatus.OK.value(), "");
        try (CloseableHttpClient httpClient = getHttpClient(builder)) {
            try (CloseableHttpResponse response = httpClient.execute(builder.getHttpRequestBase())) {
                tupleRsp = getResponse(builder, response);
                if (StringUtils.isNotBlank(tupleRsp.getSecond())) {
                    return JacksonUtil.nonEmptyMapper().fromJson(tupleRsp.getSecond(), typeReference);
                }
            }
        } catch (IOException e) {
            logger.error("*****HttpClientHelper.invoke出现错误:", e);
        } finally {
            writeLog(builder.getHttpRequestBase(), begin, tupleRsp);
        }

        return null;
    }

    private static Tuple2<Integer, String> getResponse(HttpRequestBuilder builder, CloseableHttpResponse response) {
        int status = HttpStatus.INTERNAL_SERVER_ERROR.value();
        if (response != null && response.getStatusLine() != null) {
            status = response.getStatusLine().getStatusCode();
            //if (HttpStatus.OK.value() == status) {
            //TODO 有些接口返回204,206，这里只要返回2XX都去获取一遍返回结果
            if (status >= HttpStatus.OK.value() && status < HttpStatus.MULTIPLE_CHOICES.value()) {
                try {
                    return new Tuple2<Integer, String>(status, EntityUtils.toString(response.getEntity()));
                } catch (IOException e) {
                    logger.error("*****HttpClientHelper.getResponse出现错误:", e);
                }
            }
        }

        return new Tuple2<Integer, String>(status, null);
    }

    private static void writeLog(HttpRequestBase httpRequestBase, Long begin, Tuple2<Integer, String> tupleRsp) {
        long end = System.currentTimeMillis();
        String responseContent = tupleRsp.getSecond();
        if (httpRequestBase instanceof HttpEntityEnclosingRequestBase) {
            HttpEntityEnclosingRequestBase temp = (HttpEntityEnclosingRequestBase) httpRequestBase;
            try {
                String requestBody = "";
                if (temp.getEntity() != null && temp.getEntity().getContent() != null) {
                    requestBody = StreamUtils.copyToString(temp.getEntity().getContent(), Charset.forName("UTF-8"));
                }
                logger.info("*****method:{},url:{},time:{},status:{},request:{},header:{}", httpRequestBase.getMethod(), httpRequestBase.getURI().toString(), (end - begin) / 1000.0, tupleRsp.getFirst(), requestBody, getHeader(httpRequestBase));
                logger.info("*****method:{},url:{},time:{},status:{},request:{},response:{}", httpRequestBase.getMethod(), httpRequestBase.getURI().toString(), (end - begin) / 1000.0, tupleRsp.getFirst(), requestBody, responseContent);
            } catch (IOException e) {
                logger.error("HttpClientHelper.writeLog出现错误,错误信息: {}, {}", e.getMessage(), e);
            }
        } else {
            logger.info("*****method:{},url:{},time:{},status:{},header:{}", httpRequestBase.getMethod(), httpRequestBase.getURI().toString(), (end - begin) / 1000.0, tupleRsp.getFirst(), getHeader(httpRequestBase));
            logger.info("*****method:{},url:{},time:{},status:{},response:{}", httpRequestBase.getMethod(), httpRequestBase.getURI().toString(), (end - begin) / 1000.0, tupleRsp.getFirst(), responseContent);
        }
    }

    private static String getHeader(HttpRequestBase httpRequestBase) {
        Header[] headers = httpRequestBase.getAllHeaders();
        if (ArrayUtils.isNotEmpty(headers)) {
            StringBuilder builder = new StringBuilder("[");
            for (Header header : headers) {
                builder.append(header.getName()).append("=").append(header.getValue()).append(",");
            }
            builder.append("]");

            return builder.toString();
        }

        return "";
    }
}
